import fg from "fast-glob"
import np from "normalize-path"
import path from "path"
import { toolchain } from "../toolchain.js"
import { Project, SyntaxKind } from "ts-morph"

declare interface IClassInfo {
    sourceFile: string,
    className: string,
    extends?: IClassInfo[]
}

declare interface IUIInfo {
    prefab: string,
    tsClass: IClassInfo,
    children: IUIInfo[]
}

export class UIRelations {
    private static project: Project;
    private static clsss: IClassInfo[] = [];

    public static async build(): Promise<void> {
        const tsRoot = path.join(toolchain.opts.projectRoot, 'TsScripts');
        const tsFiles = await fg('**/*.ts', { cwd: tsRoot, ignore: ['**/*.d.ts'] });

        UIRelations.project = new Project({
            tsConfigFilePath: path.join(tsRoot, 'tsconfig.json')
        });

        // const uiPathDataTs = path.join(tsRoot, 'System/data/UIPathData.ts');

        // const tabFormTs = path.join()

        for (const f of tsFiles) {
            const tsf = path.join(tsRoot, f);
            const src = UIRelations.project.getSourceFile(tsf)!;
            if (!src) {
                console.error('SourceFile not exists:', tsf);
                continue;
            }
            const classes = src.getClasses();
            for (const cls of classes) {
                // const exts = cls.getExtends();
                // if (exts) {
                //     console.log(f, cls.getName(), exts.getKindName(), exts.getText(), exts.getType().getText());
                // }
                const className = cls.getName();
                if (!className) continue;
                const clsInfo = UIRelations.getClassInfo(np(tsf), className);
                const btypes = cls.getBaseTypes();
                if (btypes.length > 0) {
                    clsInfo.extends = [];

                    btypes.forEach(v => {
                        const t = v.getText();
                        if (!t.startsWith('import(')) {
                            clsInfo.extends!.push(UIRelations.getClassInfo(clsInfo.sourceFile, t));
                        } else {
                            const mch = t.match(/^import\("(.+)"\)\.(\w+)(<.+>)?$/);
                            if (mch) {
                                const [, sourceFile, className] = mch;
                                clsInfo.extends!.push(UIRelations.getClassInfo(sourceFile + '.ts', className));
                            } else {
                                console.error('base type parse failed: ', t);
                            }
                        }
                    });
                }
            }
        }
    }

    private static isExtends(cls: IClassInfo, parent: string): boolean {
        if (cls.extends != null) {
            for (const e of cls.extends) {
                if (e.className == parent || UIRelations.isExtends(e, parent)) {
                    return true;
                }
            }
        }
        return false;
    }

    private static getClassInfo(sourceFile: string, className: string): IClassInfo {
        if (className.includes('<')) {
            className = className.substring(0, className.indexOf('<'));
        }
        let clsInfo = UIRelations.clsss.find(v => v.sourceFile == sourceFile && v.className == className);
        if (clsInfo == null) {
            clsInfo = { sourceFile, className };
            UIRelations.clsss.push(clsInfo);
        }
        return clsInfo;
    }

    public static getAllForms(): IUIInfo[] {
        const ui2Prefab: Record<string, string> = {};
        const updSrc = UIRelations.project.getSourceFileOrThrow(path.join(toolchain.opts.projectRoot, 'TsScripts/System/data/UIPathData.ts'));
        const updCls = updSrc.getClassOrThrow('UIPathData');
        const sps = updCls.getStaticProperties();
        sps.forEach(v => {
            const pd = v.asKindOrThrow(SyntaxKind.PropertyDeclaration);
            ui2Prefab[pd.getName()] = pd.getInitializerIfKindOrThrow(SyntaxKind.StringLiteral).getText();
        })

        const out: IUIInfo[] = [];
        for (const cls of UIRelations.clsss) {
            if (UIRelations.isExtends(cls, 'CommonForm')) {
                const info = UIRelations.getUIInfo(cls, out);
                info.prefab = UIRelations.getPrefabPath(cls, ui2Prefab);
                if (UIRelations.isExtends(cls, 'TabForm')) {
                    const subForms = UIRelations.getSubForms(cls);
                    // console.assert(subForms.length > 0, cls.className);
                    subForms.forEach(v => {
                        info.children.push(UIRelations.getUIInfo(v, out));
                    });
                }
            }
        }
        return out;
    }

    private static getUIInfo(cls: IClassInfo, collection: IUIInfo[]): IUIInfo {
        let info = collection.find(v => v.tsClass == cls);
        if (info == null) {
            info = { prefab: '', tsClass: cls, children: [] };
            collection.push(info);
        }
        return info;
    }

    private static getPrefabPath(cls: IClassInfo, ui2Prefab: Record<string, string>): string {
        // if (cls.className == 'CommonForm' || cls.className == 'TabForm') return '';
        const src = UIRelations.project.getSourceFileOrThrow(cls.sourceFile);
        const clsDef = src.getClassOrThrow(cls.className);
        const mthd = clsDef.getMethod('resPath');
        if (mthd != null && !mthd.isAbstract()) {
            const rsChilds = mthd.getBodyOrThrow().getChildrenOfKind(SyntaxKind.ReturnStatement);
            if (rsChilds.length > 0) {
                for (const rs of rsChilds) {
                    const rsText = rs.getText();
                    const mch = rsText.match(/return UIPathData\.(\w+)/);
                    if (mch != null) {
                        return ui2Prefab[mch[1]];
                    }
                }
            }
        }
        if (cls.extends != null) {
            for (const e of cls.extends) {
                if (UIRelations.isExtends(e, 'CommonForm')) {
                    return UIRelations.getPrefabPath(e, ui2Prefab);
                }
            }
        }
        return '';
    }

    private static getSubForms(cls: IClassInfo): IClassInfo[] {
        const out: IClassInfo[] = [];
        const src = UIRelations.project.getSourceFileOrThrow(cls.sourceFile);
        const clsDef = src.getClassOrThrow(cls.className);
        const ctors = clsDef.getConstructors();
        for (const ctor of ctors) {
            const lines = ctor.getBody()?.getChildrenOfKind(SyntaxKind.SyntaxList);
            if (lines) {
                const exps = lines[0].getChildrenOfKind(SyntaxKind.ExpressionStatement);
                for (const exp of exps) {
                    const e = exp.getExpressionIfKind(SyntaxKind.CallExpression);
                    if (e && e.getExpressionIfKind(SyntaxKind.SuperKeyword)) {
                        const args = e.getArguments();
                        args.filter(v => v.isKind(SyntaxKind.Identifier)).forEach(v => {
                            const tt = v.getType().getText();
                            const mch = tt.match(/^typeof import\("(.+)"\)\.(\w+)(<.+>)?$/);
                            if (mch) {
                                const [, sourceFile, className] = mch;
                                out.push(UIRelations.getClassInfo(sourceFile + '.ts', className));
                            }
                        });
                    }
                }
            }
        }
        return out;
    }
}
